// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector;

import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.LafManagerListener;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PropertyUtilBase;
import com.intellij.ui.*;
import com.intellij.ui.hover.TableHoverListener;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ExceptionUtilRt;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.IndentedIcon;
import com.intellij.util.ui.UIUtil;
import com.mxgraph.swing.RadEdgeComponent;
import com.mxgraph.swing.mxGraphComponent;
import com.neuronbit.businessflow.intellij.BpmIcons;
import com.neuronbit.businessflow.intellij.editor.Properties;
import com.neuronbit.businessflow.intellij.editor.UIDesignerBundle;
import com.neuronbit.businessflow.intellij.editor.swing.GuiEditor;
import com.neuronbit.businessflow.intellij.editor.swing.editor.*;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.properties.*;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.renderers.LabelPropertyRenderer;
import com.neuronbit.businessflow.intellij.model.JavaMethodTask;
import com.neuronbit.businessflow.intellij.model.StatefulTask;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.plaf.TableUI;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.*;

/**
 * The type Property inspector table.
 */
public final class PropertyInspectorTable extends JBTable implements DataProvider {
    private static final Logger LOG = Logger.getInstance(PropertyInspectorTable.class);

    private static final int PROPERTY_INDENT = 11;

    /**
     * The constant DATA_KEY.
     */
    public static final DataKey<PropertyInspectorTable> DATA_KEY = DataKey.create(PropertyInspectorTable.class.getName());

    private static final Color SYNTHETIC_PROPERTY_BACKGROUND = new JBColor(Gray._230, UIUtil.getPanelBackground().brighter());
    private static final Color SYNTHETIC_SUBPROPERTY_BACKGROUND = new JBColor(Gray._240, UIUtil.getPanelBackground().brighter());

    private static final Color CATEGORY_BACKGROUND = new JBColor(Gray._137, UIUtil.getPanelBackground().darker());

    private final mxGraphComponent myComponentTree;
    private final ArrayList<Property> myProperties;
    private final MyModel myModel;
    private final MyCompositeTableCellRenderer myCellRenderer;
    private final MyCellEditor myCellEditor;
    private GuiEditor myEditor;
    /**
     * This listener gets notifications from current property editor
     */
    private final MyPropertyEditorListener myPropertyEditorListener;
    /**
     * Updates UIs of synthetic properties
     */
    private Disposable myLafManagerDisposable;
    private final MyLafManagerListener myLafManagerListener;
    /**
     * This is property exists in this map then it's expanded.
     * It means that its children is visible.
     */
    private final HashSet<String> myExpandedProperties;
    /**
     * Component to be edited
     */
    @NotNull
    private final List<RadComponent> mySelection = new ArrayList<>();
    /**
     * If true then inspector will show "expert" properties
     */
    private boolean myShowExpertProperties;

    private final Map<HighlightSeverity, SimpleTextAttributes> myHighlightAttributes = new HashMap<>();
    private final Map<HighlightSeverity, SimpleTextAttributes> myModifiedHighlightAttributes = new HashMap<>();

    private final FlowClassToBindProperty myFlowClassToBindProperty;
//    private final BindingProperty myBindingProperty;
//    private final BorderProperty myBorderProperty;
//    private final LayoutManagerProperty myLayoutManagerProperty = new LayoutManagerProperty();
//    private final ButtonGroupProperty myButtonGroupProperty = new ButtonGroupProperty();

    private boolean myInsideSynch;
    private boolean myStoppingEditing;
    private final Project myProject;

    @NonNls
    private static final String ourHelpID = "guiDesigner.uiTour.inspector";

    /**
     * Instantiates a new Property inspector table.
     *
     * @param project       the project
     * @param componentTree the component tree
     */
    PropertyInspectorTable(Project project, @NotNull final mxGraphComponent componentTree) {
        myProject = project;
        myFlowClassToBindProperty = new FlowClassToBindProperty(project);
//        myBindingProperty = new BindingProperty(project);
//        myBorderProperty = new BorderProperty(project);

        myPropertyEditorListener = new MyPropertyEditorListener();
        myLafManagerListener = new MyLafManagerListener();
        myComponentTree = componentTree;
        myProperties = new ArrayList<>();
        myExpandedProperties = new HashSet<>();
        myModel = new MyModel();
        setModel(myModel);
        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        myCellRenderer = new MyCompositeTableCellRenderer();
        myCellEditor = new MyCellEditor();

        TableHoverListener.DEFAULT.removeFrom(this);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(final MouseEvent e) {
                final int row = rowAtPoint(e.getPoint());
                final int column = columnAtPoint(e.getPoint());
                if (row == -1 || column == -1) {
                    return;
                }
                final Property property = myProperties.get(row);
                int indent = getPropertyIndentDepth(property) * getScaledPropertyIndent();
                final Rectangle rect = getCellRect(row, convertColumnIndexToView(0), false);

                Component rendererComponent = myCellRenderer.getTableCellRendererComponent(PropertyInspectorTable.this,
                        property, false, false, row, column);
                if (!rect.contains(e.getX(), e.getY()) ||
                        !(rendererComponent instanceof ColoredTableCellRenderer) ||
                        ((ColoredTableCellRenderer) rendererComponent).findFragmentAt(e.getX()) != SimpleColoredComponent.FRAGMENT_ICON ||
                        e.getX() < rect.x + indent) {
                    return;
                }

                final Property[] children = getPropChildren(property);
                if (children.length == 0) {
                    return;
                }

                if (isPropertyExpanded(property)) {
                    collapseProperty(row);
                } else {
                    expandProperty(row);
                }
            }
        });

        new DoubleClickListener() {
            @Override
            protected boolean onDoubleClick(@NotNull MouseEvent e) {
                int row = rowAtPoint(e.getPoint());
                int column = columnAtPoint(e.getPoint());
                if (row >= 0 && column == 0) {
                    final Property property = myProperties.get(row);
                    if (getPropChildren(property).length == 0) {
                        startEditing(row);
                        return true;
                    }
                }
                return false;
            }
        }.installOn(this);


        //final AnAction quickJavadocAction = ActionManager.getInstance().getAction(IdeActions.ACTION_QUICK_JAVADOC);
        //new ShowJavadocAction().registerCustomShortcutSet(quickJavadocAction.getShortcutSet(), this);

        // Popup menu
        PopupHandler.installPopupMenu(
                this,
                IdeActions.GROUP_GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP,
                ActionPlaces.GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP);
    }

    /**
     * Sets editor.
     *
     * @param editor the editor
     */
    public void setEditor(final GuiEditor editor) {
        finishEditing();
        myEditor = editor;
        if (myEditor == null) {
            mySelection.clear();
            myProperties.clear();
            myModel.fireTableDataChanged();
        }
    }

    /**
     * Gets selected introspected property.
     *
     * @return currently selected {@link IntrospectedProperty} or {@code null} if nothing selected or synthetic property is selected.
     */
    @Nullable
    public IntrospectedProperty getSelectedIntrospectedProperty() {
        Property property = getSelectedProperty();
        if (!(property instanceof IntrospectedProperty)) {
            return null;
        }

        return (IntrospectedProperty) property;
    }

    /**
     * Gets selected property.
     *
     * @return the selected property
     */
    @Nullable
    public Property getSelectedProperty() {
        final int selectedRow = getSelectedRow();
        if (selectedRow < 0 || selectedRow >= getRowCount()) {
            return null;
        }

        return myProperties.get(selectedRow);
    }

    /**
     * Gets selected rad component class name.
     *
     * @return the selected rad component class name
     */
    public @Nullable String getSelectedRadComponentClassName() {
        if (mySelection.size() == 0) return null;
        String className = mySelection.get(0).getComponentClassName();
        for (int i = 1; i < mySelection.size(); i++) {
            if (!Objects.equals(mySelection.get(i).getComponentClassName(), className)) {
                return null;
            }
        }
        return className;
    }

    @Override
    public @Nullable Object getData(@NotNull String dataId) {
        if (DATA_KEY.is(dataId)) {
            return this;
        } else if (GuiEditor.DATA_KEY.is(dataId)) {
            return myEditor;
        } else if (PlatformCoreDataKeys.FILE_EDITOR.is(dataId)) {
//      GuiEditor designer = myProject.isDisposed() ? null : DesignerToolWindowManager.getInstance(myProject).getActiveFormEditor();
//      return designer == null ? null : designer.getEditor();
        } else if (PlatformCoreDataKeys.BGT_DATA_PROVIDER.is(dataId)) {
            IntrospectedProperty<?> property = getSelectedIntrospectedProperty();
            String className = getSelectedRadComponentClassName();
            return (DataProvider) slowId -> getSlowData(slowId, className, property);
        } else if (PlatformCoreDataKeys.HELP_ID.is(dataId)) {
            return ourHelpID;
        }
        return null;
    }

    private @Nullable Object getSlowData(@NotNull String dataId,
                                         @Nullable String radComponentClassName,
                                         @Nullable IntrospectedProperty<?> introspectedProperty) {
        if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
            if (introspectedProperty == null || radComponentClassName == null || myEditor == null) {
                return null;
            }
            PsiClass aClass = JavaPsiFacade.getInstance(myEditor.getProject()).findClass(
                    radComponentClassName, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(myEditor.getModule()));
            if (aClass == null) {
                return null;
            }
            PsiMethod getter = PropertyUtilBase.findPropertyGetter(aClass, introspectedProperty.getName(), false, true);
            if (getter != null) {
                return getter;
            }
            return PropertyUtilBase.findPropertySetter(aClass, introspectedProperty.getName(), false, true);
        } else if (CommonDataKeys.PSI_FILE.is(dataId)) {
            return myEditor != null ? PsiManager.getInstance(myEditor.getProject()).findFile(myEditor.getMyFile()) : null;
        }
        return null;
    }

    /**
     * Sets whenther "expert" properties are shown or not
     *
     * @param showExpertProperties the show expert properties
     */
    void setShowExpertProperties(final boolean showExpertProperties) {
        if (myShowExpertProperties == showExpertProperties) {
            return;
        }
        myShowExpertProperties = showExpertProperties;
        if (myEditor != null) {
            synchWithTree(true);
        }
    }

    @Override
    public void addNotify() {
        super.addNotify();

        if (myLafManagerDisposable != null) {
            Disposer.dispose(myLafManagerDisposable);
        }
        myLafManagerDisposable = Disposer.newDisposable();
        ApplicationManager.getApplication().getMessageBus().connect(myLafManagerDisposable)
                .subscribe(LafManagerListener.TOPIC, myLafManagerListener);
    }

    @Override
    public void removeNotify() {
        if (myLafManagerDisposable != null) {
            Disposer.dispose(myLafManagerDisposable);
            myLafManagerDisposable = null;
        }

        super.removeNotify();
    }

    /**
     * Standard JTable's UI has non convenient keybinding for
     * editing. Therefore we have to replace some standard actions.
     */
    @Override
    public void setUI(final TableUI ui) {
        super.setUI(ui);

        // Customize action and input maps
        @NonNls final ActionMap actionMap = getActionMap();
        @NonNls final InputMap focusedInputMap = getInputMap(JComponent.WHEN_FOCUSED);
        @NonNls final InputMap ancestorInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        actionMap.put(TableActions.Up.ID, new MySelectPreviousRowAction());

        actionMap.put(TableActions.Down.ID, new MySelectNextRowAction());

        actionMap.put("startEditing", new MyStartEditingAction());
        focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "startEditing");
        ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));

        actionMap.put("smartEnter", new MyEnterAction());
        focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "smartEnter");
        ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));

        focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
        ancestorInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");

        actionMap.put("expandCurrent", new MyExpandCurrentAction(true));
        focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "expandCurrent");
        ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));

        actionMap.put("collapseCurrent", new MyExpandCurrentAction(false));
        focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "collapseCurrent");
        ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
    }

    @Override
    public void setValueAt(final Object aValue, final int row, final int column) {
        final Property property = myProperties.get(row);
        super.setValueAt(aValue, row, column);
        // We need to repaint whole inspector because change of one property
        // might causes change of another property.
        if (property.needRefreshPropertyList()) {
            synchWithTree(true);
        }
        repaint();
    }

    /**
     * Gets first selected component from ComponentTree and sets it for editing.
     * The method tries to keep selection in the list, so if new component has the property
     * which is already selected then the new value will be
     * also selected. It is very convenient.
     *
     * @param forceSynch if {@code false} and selected component in the ComponentTree                   is the same as current component in the PropertyInspector then method does                   nothing such sace. If {@code true} then inspector is forced to resynch.
     */
    public void synchWithTree(final boolean forceSynch) {
        if (myInsideSynch) {
            return;
        }
        myInsideSynch = true;
        try {
            RadComponent[] newSelection = myComponentTree.getSelectedComponents();
            if (!forceSynch && mySelection.size() == newSelection.length) {
                boolean anyChanges = false;
                for (RadComponent c : newSelection) {
                    if (!mySelection.contains(c)) {
                        anyChanges = true;
                        break;
                    }
                }
                if (!anyChanges) return;
            }

            mySelection.clear();
            Collections.addAll(mySelection, newSelection);

            if (isEditing()) {
                cellEditor.stopCellEditing();
            }

            // Store selected property
            final int selectedRow = getSelectedRow();
            Property selectedProperty = null;
            if (selectedRow >= 0 && selectedRow < myProperties.size()) {
                selectedProperty = myProperties.get(selectedRow);
            }

            collectPropertiesForSelection();
            myModel.fireTableDataChanged();

            // Try to restore selection
            final ArrayList<Property> reversePath = new ArrayList<>(2);
            while (selectedProperty != null) {
                reversePath.add(selectedProperty);
                selectedProperty = selectedProperty.getParent();
            }
            int indexToSelect = -1;
            for (int i = reversePath.size() - 1; i >= 0; i--) {
                final Property property = reversePath.get(i);
                int index = findPropertyByName(myProperties, property.getName());
                if (index == -1 && indexToSelect != -1) { // try to expand parent and try again
                    expandProperty(indexToSelect);
                    index = findPropertyByName(myProperties, property.getName());
                    if (index != -1) {
                        indexToSelect = index;
                    } else {
                        break;
                    }
                } else {
                    indexToSelect = index;
                }
            }

            if (indexToSelect != -1) {
                getSelectionModel().setSelectionInterval(indexToSelect, indexToSelect);
            } else if (getRowCount() > 0) {
                // Select first row if it's impossible to restore selection
                getSelectionModel().setSelectionInterval(0, 0);
            }
            TableUtil.scrollSelectionToVisible(this);
        } finally {
            myInsideSynch = false;
        }
    }

    private void collectPropertiesForSelection() {
        myProperties.clear();
        if (mySelection.size() > 0) {
            collectProperties(mySelection.get(0), myProperties);

            for (int propIndex = myProperties.size() - 1; propIndex >= 0; propIndex--) {
                if (!myProperties.get(propIndex).appliesToSelection(mySelection)) {
                    myProperties.remove(propIndex);
                }
            }

            for (int i = 1; i < mySelection.size(); i++) {
                ArrayList<Property> otherProperties = new ArrayList<>();
                collectProperties(mySelection.get(i), otherProperties);
                for (int propIndex = myProperties.size() - 1; propIndex >= 0; propIndex--) {
                    final Property prop = myProperties.get(propIndex);
                    int otherPropIndex = findPropertyByName(otherProperties, prop.getName());
                    if (otherPropIndex < 0) {
                        myProperties.remove(propIndex);
                        continue;
                    }
                    final Property otherProp = otherProperties.get(otherPropIndex);
                    if (!otherProp.getClass().equals(prop.getClass())) {
                        myProperties.remove(propIndex);
                        continue;
                    }
                    Property[] children = prop.getChildren(mySelection.get(0));
                    Property[] otherChildren = otherProp.getChildren(mySelection.get(i));
                    if (children.length != otherChildren.length) {
                        myProperties.remove(propIndex);
                        continue;
                    }
                    for (int childIndex = 0; childIndex < children.length; childIndex++) {
                        if (!Objects.equals(children[childIndex].getName(), otherChildren[childIndex].getName())) {
                            myProperties.remove(propIndex);
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * @return index of the property with specified {@code name}.
     * If there is no such property then the method returns {@code -1}.
     */
    private static int findPropertyByName(final ArrayList<Property> properties, final String name) {
        for (int i = properties.size() - 1; i >= 0; i--) {
            final Property property = properties.get(i);
            if (property.getName().equals(name)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Populates result list with the properties available for the specified
     * component
     */
    private void collectProperties(final RadComponent component, final ArrayList<Property> result) {
        if (component == null) {
            throw new RuntimeException();
        }
        if (component instanceof RadRootContainer) {
            //addProperty(result, myClassToBindProperty);
        }

        addProperty(result, new ReadOnlyProperty(null, "Basic") {
            @Override
            public @NotNull PropertyRenderer getRenderer() {
                return new LabelPropertyRenderer("");
            }
        });
        if (component.hasIntrospectedProperties()) {
            final Class<?> componentClass = component.getComponentClass();
            final IntrospectedProperty[] introspectedProperties =
                    Palette.getInstance(myEditor.getProject()).getIntrospectedProperties(component);
            final Properties properties = Properties.getInstance();
            for (final IntrospectedProperty property : introspectedProperties) {
                if (!property.appliesTo(component)) continue;
                if (!myShowExpertProperties
                        && properties.isExpertProperty(component.getModule(), componentClass, property.getName())
                        && !isModifiedForSelection(property)) {
                    continue;
                }
                addProperty(result, property);
            }
        }

        Class<?> value = component.getComponentClass();
        if (StatefulTask.class.isAssignableFrom(value)) {
            addProperty(result, new ReadOnlyProperty(null, "State Checker") {
                @Override
                public @NotNull PropertyRenderer getRenderer() {
                    return new LabelPropertyRenderer("");
                }
            });
            StateCheckerClassToBindProperty exceptionHandlerClassToBindProperty = new StateCheckerClassToBindProperty(myProject);
            addProperty(result, exceptionHandlerClassToBindProperty);
            addProperty(result, new StateCheckerMethodToBindProperty("method"));
            RadStatefulTaskComponent statefulTaskComponent = (RadStatefulTaskComponent) component;
            addProperty(result, new StateCheckerMethodParamsProperty(statefulTaskComponent, "parameters"));
        }
        if (JavaMethodTask.class.isAssignableFrom(value)) {
            addProperty(result, new ReadOnlyProperty(null, "Callable") {
                @Override
                public @NotNull PropertyRenderer getRenderer() {
                    return new LabelPropertyRenderer("");
                }
            });
            CallableClassToBindProperty callableClassToBindProperty = new CallableClassToBindProperty(myProject);
            addProperty(result, callableClassToBindProperty);
            addProperty(result, new CallableMethodToBindProperty("method"));

            RadJavaMethodComponent javaMethodComponent = (RadJavaMethodComponent) component;
            addProperty(result, new CallableMethodParamsProperty(javaMethodComponent, "parameters"));
            if (javaMethodComponent.isFlow()) {
                addProperty(result, new CallableMethodReturnTypeProperty(javaMethodComponent, component.getProject(), "return"));
            }
            addProperty(result, new ReadOnlyProperty(null, "Exception Handler") {
                @Override
                public @NotNull PropertyRenderer getRenderer() {
                    return new LabelPropertyRenderer("");
                }
            });
            ExceptionHandlerClassToBindProperty exceptionHandlerClassToBindProperty = new ExceptionHandlerClassToBindProperty(myProject);
            addProperty(result, exceptionHandlerClassToBindProperty);
            addProperty(result, new ExceptionHandlerMethodToBindProperty("method"));
        }

        if (component instanceof RadEdgeComponent) {
            addProperty(result, new EdgeLabelProperty());
        }
//      if (!(component instanceof RadVSpacer || component instanceof RadHSpacer)){
//        addProperty(result, myBindingProperty);
//        addProperty(result, CustomCreateProperty.getInstance(myProject));
//      }

//      if(component instanceof RadContainer container){
//        if (container.getLayoutManager().getName() != null) {
//          addProperty(result, myLayoutManagerProperty);
//        }
//        addProperty(result, myBorderProperty);
//
//        final Property[] containerProperties = container.getLayoutManager().getContainerProperties(myProject);
//        addApplicableProperties(containerProperties, container, result);
//      }

//      final RadContainer parent = component.getParent();
//      if (parent != null) {
//        final Property[] properties = parent.getLayoutManager().getComponentProperties(myProject, component);
//        addApplicableProperties(properties, component, result);
//      }

//      if (component.getDelegee() instanceof AbstractButton && !(component.getDelegee() instanceof JButton)) {
//        addProperty(result, myButtonGroupProperty);
//      }

//      if (!(component instanceof RadVSpacer || component instanceof RadHSpacer)) {
//        addProperty(result, ClientPropertiesProperty.getInstance(myProject));
//      }
    }

    private void addApplicableProperties(final Property[] containerProperties,
                                         final RadComponent component,
                                         final ArrayList<Property> result) {
        for (Property prop : containerProperties) {
            //noinspection unchecked
            if (prop.appliesTo(component)) {
                addProperty(result, prop);
            }
        }
    }

    private void addProperty(final ArrayList<Property> result, final Property property) {
        result.add(property);
        if (isPropertyExpanded(property)) {
            for (Property child : getPropChildren(property)) {
                addProperty(result, child);
            }
        }
    }

    private boolean isPropertyExpanded(final Property property) {
        return myExpandedProperties.contains(getDottedName(property));
    }

    private static String getDottedName(final Property property) {
        final Property parent = property.getParent();
        if (parent != null) {
            return parent.getName() + "." + property.getName();
        }
        return property.getName();
    }

    private static int getPropertyIndentDepth(final Property property) {
        Property parent = property.getParent();
        int depth = 0;
        while (parent != null) {
            depth++;
            parent = parent.getParent();
        }
//        if (parent != null) {
//            return parent.getParent() != null ? 2 : 1;
//        }
        return depth;
    }

    private static int getScaledPropertyIndent() {
        return JBUIScale.scale(PROPERTY_INDENT);
    }

    private Property[] getPropChildren(final Property property) {
        return property.getChildren(mySelection.get(0));
    }

    @Override
    public TableCellEditor getCellEditor(final int row, final int column) {
        final PropertyEditor editor = myProperties.get(row).getEditor();
        editor.removePropertyEditorListener(myPropertyEditorListener); // we do not need to add listener on every invocation
        editor.addPropertyEditorListener(myPropertyEditorListener);
        myCellEditor.setEditor(editor);
        return myCellEditor;
    }

    @Override
    public TableCellRenderer getCellRenderer(final int row, final int column) {
        return myCellRenderer;
    }

    /*
     * This method is overriden due to bug in the JTree. The problem is that
     * JTree does not properly repaint edited cell if the editor is opaque or
     * has opaque child components.
     */
    @Override
    public boolean editCellAt(final int row, final int column, final EventObject e) {
        final boolean result = super.editCellAt(row, column, e);
        final Rectangle cellRect = getCellRect(row, column, true);
        repaint(cellRect);
        return result;
    }

    /**
     * Starts editing property with the specified {@code index}.
     * The method does nothing is property isn't editable.
     */
    private void startEditing(final int index) {
        final Property property = myProperties.get(index);
        final PropertyEditor editor = property.getEditor();
        if (editor == null) {
            return;
        }
        editCellAt(index, convertColumnIndexToView(1));
        LOG.assertTrue(editorComp != null);
        // Now we have to request focus into the editor component
        JComponent prefComponent = editor.getPreferredFocusedComponent((JComponent) editorComp);
        if (prefComponent == null) { // use default policy to find preferred focused component
            prefComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent) editorComp);
        }
        if (prefComponent != null) {
            prefComponent.requestFocusInWindow();
        }
    }

    private void finishEditing() {
        if (editingRow == -1) {
            return;
        }
        editingStopped(new ChangeEvent(cellEditor));
    }

    @Override
    public void editingStopped(final ChangeEvent ignored) {
        LOG.assertTrue(isEditing());
        LOG.assertTrue(editingRow != -1);
        if (myStoppingEditing) {
            return;
        }
        myStoppingEditing = true;
        final Property property = myProperties.get(editingRow);
        final PropertyEditor editor = property.getEditor();
        editor.removePropertyEditorListener(myPropertyEditorListener);
        try {
            if (myEditor != null && !myEditor.isUndoRedoInProgress()) {
                final Object value = editor.getValue();
                setValueAt(value, editingRow, editingColumn);
            }
        } catch (final Exception exc) {
            showInvalidInput(exc);
        } finally {
            removeEditor();
            myStoppingEditing = false;
        }
    }

    private static void showInvalidInput(final Exception exc) {
        final Throwable cause = exc.getCause();
        String message;
        if (cause != null) {
            message = cause.getMessage();
        } else {
            message = exc.getMessage();
        }
        if (message == null || message.length() == 0) {
            message = UIDesignerBundle.message("error.no.message");
        }
        Messages.showMessageDialog(UIDesignerBundle.message("error.setting.value", message),
                UIDesignerBundle.message("title.invalid.input"), Messages.getErrorIcon());
    }

    /**
     * Expands property with the specified index. The method fires event that
     * model changes and keeps currently selected row.
     */
    private void expandProperty(final int index) {
        final int selectedRow = getSelectedRow();

        // Expand property
        final Property property = myProperties.get(index);
        final String dottedName = getDottedName(property);

        // it's possible that property was expanded and we switched to a component which doesn't have this property
        if (myExpandedProperties.contains(dottedName)) return;
        myExpandedProperties.add(dottedName);

        final Property[] children = getPropChildren(property);
        for (int i = 0; i < children.length; i++) {
            myProperties.add(index + i + 1, children[i]);
        }
        myModel.fireTableDataChanged();

        // Restore selected row
        if (selectedRow != -1) {
            getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
        }
    }

    /**
     * Collapse property with the specified index. The method fires event that
     * model changes and keeps currently selected row.
     */
    private void collapseProperty(final int index) {
        final int selectedRow = getSelectedRow();

        // Expand property
        final Property property = myProperties.get(index);
        LOG.assertTrue(isPropertyExpanded(property));
        myExpandedProperties.remove(getDottedName(property));

        final Property[] children = getPropChildren(property);
        for (int i = 0; i < children.length; i++) {
            Property goingToRemoved = myProperties.get(index + 1);
            if (goingToRemoved != null && isPropertyExpanded(goingToRemoved)) {
                collapseProperty(index + 1);
            }
            myProperties.remove(index + 1);
        }
        myModel.fireTableDataChanged();

        // Restore selected row
        if (selectedRow != -1) {
            getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
        }
    }

    /**
     * Gets error info for row.
     *
     * @param row the row
     * @return the error info for row
     */
    @Nullable
    ErrorInfo getErrorInfoForRow(final int row) {
        LOG.assertTrue(row < myProperties.size());
        if (mySelection.size() != 1) {
            return null;
        }
        RadComponent component = mySelection.get(0);
        final Property property = myProperties.get(row);
        ErrorInfo errorInfo = null;
        if (myFlowClassToBindProperty.equals(property)) {
            errorInfo = (ErrorInfo) component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_CLASS_TO_BIND_ERROR);
//        } else if (myBindingProperty.equals(property)) {
//            errorInfo = (ErrorInfo) component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_BINDING_ERROR);
        } else {
            //noinspection unchecked
            ArrayList<ErrorInfo> errors = (ArrayList<ErrorInfo>) component.getClientProperty(ErrorAnalyzer.CLIENT_PROP_ERROR_ARRAY);
            if (errors != null) {
                for (ErrorInfo err : errors) {
                    if (property.getName().equals(err.getPropertyName())) {
                        errorInfo = err;
                        break;
                    }
                }
            }
        }
        return errorInfo;
    }

    /**
     * @return first error for the property at the specified row. If component doesn't contain
     * any error then the method returns {@code null}.
     */
    @Nullable
    private String getErrorForRow(final int row) {
        LOG.assertTrue(row < myProperties.size());
        final ErrorInfo errorInfo = getErrorInfoForRow(row);
        return errorInfo != null ? errorInfo.myDescription : null;
    }

    @Override
    public String getToolTipText(final MouseEvent e) {
        final int row = rowAtPoint(e.getPoint());
        if (row == -1) {
            return null;
        }
        return getErrorForRow(row);
    }

    private Object getSelectionValue(final Property property) {
        if (mySelection.size() == 0) {
            return null;
        }
        //noinspection unchecked
        Object result = property.getValue(mySelection.get(0));
        for (int i = 1; i < mySelection.size(); i++) {
            Object otherValue = null;
            if (property instanceof IntrospectedProperty) {
                IntrospectedProperty[] props = Palette.getInstance(myProject).getIntrospectedProperties(mySelection.get(i));
                for (IntrospectedProperty otherProperty : props) {
                    if (otherProperty.getName().equals(property.getName())) {
                        otherValue = otherProperty.getValue(mySelection.get(i));
                        break;
                    }
                }
            } else {
                //noinspection unchecked
                otherValue = property.getValue(mySelection.get(i));
            }
            if (!Comparing.equal(result, otherValue)) {
                return null;
            }
        }
        return result;
    }

    /**
     * @return false if some of the set value operations have failed; true if everything successful
     */
    private boolean setSelectionValue(Property property, Object newValue) {
        if (!setPropValue(property, mySelection.get(0), newValue)) return false;
        for (int i = 1; i < mySelection.size(); i++) {
            if (property instanceof IntrospectedProperty) {
                IntrospectedProperty[] props = Palette.getInstance(myProject).getIntrospectedProperties(mySelection.get(i));
                for (IntrospectedProperty otherProperty : props) {
                    if (otherProperty.getName().equals(property.getName())) {
                        if (!setPropValue(otherProperty, mySelection.get(i), newValue)) return false;
                        break;
                    }
                }
            } else {
                if (!setPropValue(property, mySelection.get(i), newValue)) return false;
            }
        }
        return true;
    }

    private static boolean setPropValue(final Property property, final RadComponent c, final Object newValue) {
        try {
            //noinspection unchecked
            property.setValue(c, newValue);
        } catch (Throwable e) {
            LOG.debug(e);
            String message = ExceptionUtilRt.unwrapInvocationTargetException(e).getMessage();
            Messages.showMessageDialog(message, UIDesignerBundle.message("title.invalid.input"), Messages.getErrorIcon());
            return false;
        }
        return true;
    }

    /**
     * Is modified for selection boolean.
     *
     * @param property the property
     * @return the boolean
     */
    public boolean isModifiedForSelection(final Property property) {
        for (RadComponent c : mySelection) {
            //noinspection unchecked
            if (property.isModified(c)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adapter to TableModel
     */
    private final class MyModel extends AbstractTableModel {
        private final String[] myColumnNames;

        /**
         * Instantiates a new My model.
         */
        MyModel() {
            myColumnNames = new String[]{
                    UIDesignerBundle.message("column.property"),
                    UIDesignerBundle.message("column.value")};
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(final int column) {
            return myColumnNames[column];
        }

        @Override
        public int getRowCount() {
            return myProperties.size();
        }

        @Override
        public boolean isCellEditable(final int row, final int column) {
            return column == 1 && myProperties.get(row).getEditor() != null;
        }

        @Override
        public Object getValueAt(final int row, final int column) {
            return myProperties.get(row);
        }

        @Override
        public void setValueAt(final Object newValue, final int row, final int column) {
            if (column != 1) {
                throw new IllegalArgumentException("wrong index: " + column);
            }
            setValueAtRow(row, newValue);
        }

        /**
         * Sets value at row.
         *
         * @param row      the row
         * @param newValue the new value
         * @return the value at row
         */
        boolean setValueAtRow(final int row, final Object newValue) {
            final Property property = myProperties.get(row);

            // Optimization: do nothing if value doesn't change
            final Object oldValue = getSelectionValue(property);
            boolean retVal = true;
            if (!Comparing.equal(oldValue, newValue)) {
                final GuiEditor editor = myEditor;
                if (!editor.ensureEditable()) {
                    return false;
                }
                final Ref<Boolean> result = new Ref<>(Boolean.FALSE);
                CommandProcessor.getInstance().executeCommand(myProject, () -> {
                    result.set(setSelectionValue(property, newValue));
                    editor.refreshAndSave(false);
                }, UIDesignerBundle.message("command.set.property.value"), null);

                retVal = result.get().booleanValue();
            }
            if (property.needRefreshPropertyList() && retVal) {
                synchWithTree(true);
            }
            return retVal;
        }
    }

    private final class MyPropertyEditorListener implements PropertyEditorListener {
        @Override
        public void valueCommitted(@NotNull final PropertyEditor source, final boolean continueEditing, final boolean closeEditorOnError) {
            if (isEditing()) {
                final Object value;
                final TableCellEditor tableCellEditor = cellEditor;
                try {
                    value = tableCellEditor.getCellEditorValue();
                } catch (final Exception exc) {
                    showInvalidInput(exc);
                    return;
                }
                boolean valueAccepted = myModel.setValueAtRow(editingRow, value);
                if (valueAccepted) {
                    if (!continueEditing) tableCellEditor.stopCellEditing();
                } else {
                    if (closeEditorOnError) tableCellEditor.cancelCellEditing();
                }
            }
        }

        @Override
        public void editingCanceled(@NotNull final PropertyEditor source) {
            if (isEditing()) {
                cellEditor.cancelCellEditing();
            }
        }
    }

    private final class MyCompositeTableCellRenderer implements TableCellRenderer {
        /**
         * This renderer paints first column with property names
         */
        private final ColoredTableCellRenderer myPropertyNameRenderer;
        private final ColoredTableCellRenderer myErrorRenderer;
        private final Icon myExpandIcon;
        private final Icon myCollapseIcon;
        private final Icon myIndentedExpandIcon;
        private final Icon myIndentedCollapseIcon;
        private final Icon[] myIndentIcons = new Icon[4];

        /**
         * Instantiates a new My composite table cell renderer.
         */
        MyCompositeTableCellRenderer() {
            myPropertyNameRenderer = new ColoredTableCellRenderer() {
                @Override
                protected void customizeCellRenderer(
                        final @NotNull JTable table,
                        final Object value,
                        final boolean selected,
                        final boolean hasFocus,
                        final int row,
                        final int column
                ) {
                    // We will append text later in the
                    setPaintFocusBorder(false);
                    setFocusBorderAroundIcon(true);
                }
            };

            myErrorRenderer = new ColoredTableCellRenderer() {
                @Override
                protected void customizeCellRenderer(@NotNull JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
                    setPaintFocusBorder(false);
                }
            };

            myExpandIcon = BpmIcons.ExpandNode;
            myCollapseIcon = BpmIcons.CollapseNode;
            for (int i = 0; i < myIndentIcons.length; i++) {
                myIndentIcons[i] = EmptyIcon.create(myExpandIcon.getIconWidth() + getScaledPropertyIndent() * i, myExpandIcon.getIconHeight());
            }
            myIndentedExpandIcon = new IndentedIcon(myExpandIcon, PROPERTY_INDENT);
            myIndentedCollapseIcon = new IndentedIcon(myCollapseIcon, PROPERTY_INDENT);
        }

        @Override
        public Component getTableCellRendererComponent(
                final JTable table,
                @NotNull final Object value,
                final boolean selected,
                final boolean hasFocus,
                final int row,
                int column
        ) {
            myPropertyNameRenderer.getTableCellRendererComponent(table, value, selected, hasFocus, row, column);

            column = table.convertColumnIndexToModel(column);
            final Property property = (Property) value;

            final Color background;
            final Property parent = property.getParent();
            if (property instanceof IntrospectedProperty) {
                background = table.getBackground();
            } else if (property instanceof ReadOnlyProperty) {
                background = CATEGORY_BACKGROUND;
            } else {
                background = parent == null ? SYNTHETIC_PROPERTY_BACKGROUND : SYNTHETIC_SUBPROPERTY_BACKGROUND;
            }

            if (!selected) {
                myPropertyNameRenderer.setBackground(background);
            }

            if (column == 0) { // painter for first column
                SimpleTextAttributes attrs = getTextAttributes(row, property);
                myPropertyNameRenderer.append(property.getName(), attrs);

                // 2. Icon
                if (getPropChildren(property).length > 0) {
                    // This is composite property and we have to show +/- sign
                    if (parent != null) {
                        if (isPropertyExpanded(property)) {
                            IndentedIcon indentedIcon = new IndentedIcon(myCollapseIcon, PROPERTY_INDENT * getPropertyIndentDepth(property));
                            myPropertyNameRenderer.setIcon(indentedIcon);
//                            myPropertyNameRenderer.setIcon(myIndentedCollapseIcon);
                        } else {
                            IndentedIcon indentedIcon = new IndentedIcon(myExpandIcon, PROPERTY_INDENT * getPropertyIndentDepth(property));
                            myPropertyNameRenderer.setIcon(indentedIcon);
//                            myPropertyNameRenderer.setIcon(myIndentedExpandIcon);
                        }
                    } else {
                        if (isPropertyExpanded(property)) {
                            myPropertyNameRenderer.setIcon(myCollapseIcon);
                        } else {
                            myPropertyNameRenderer.setIcon(myExpandIcon);
                        }
                    }
                } else if (property instanceof ReadOnlyProperty) {

                } else {
                    // If property doesn't have children then we have shift its text
                    // to the right
                    myPropertyNameRenderer.setIcon(myIndentIcons[getPropertyIndentDepth(property)]);
                }
            } else if (column == 1) { // painter for second column
                try {
                    final PropertyRenderer renderer = property.getRenderer();
                    //noinspection unchecked
                    final JComponent component = renderer.getComponent(
                            myEditor.getRootContainer(),
                            getSelectionValue(property),
                            selected,
                            hasFocus);
                    if (!selected) {
                        component.setBackground(background);
                    }
                    if (isModifiedForSelection(property)) {
                        component.setFont(table.getFont().deriveFont(Font.BOLD));
                    } else {
                        component.setFont(table.getFont());
                    }

                    return component;
                } catch (Exception ex) {
                    LOG.debug(ex);
                    myErrorRenderer.clear();
                    myErrorRenderer.append(UIDesignerBundle.message("error.getting.value", ex.getMessage()), SimpleTextAttributes.ERROR_ATTRIBUTES);
                    return myErrorRenderer;
                }
            } else {
                throw new IllegalArgumentException("wrong column: " + column);
            }

            if (!selected) {
                myPropertyNameRenderer.setForeground(PropertyInspectorTable.this.getForeground());
                if (property instanceof IntrospectedProperty) {
                    final RadComponent component = mySelection.get(0);
                    final Class componentClass = component.getComponentClass();
                    if (Properties.getInstance().isExpertProperty(component.getModule(), componentClass, property.getName())) {
                        myPropertyNameRenderer.setForeground(Color.LIGHT_GRAY);
                    }
                }
            }

            return myPropertyNameRenderer;
        }

        private SimpleTextAttributes getTextAttributes(final int row, final Property property) {
            // 1. Text
            ErrorInfo errInfo = getErrorInfoForRow(row);

            SimpleTextAttributes result;
            boolean modified;
            try {
                modified = isModifiedForSelection(property);
            } catch (Exception ex) {
                // ignore exceptions here - they'll be reported as red property values
                modified = false;
            }
            if (errInfo == null) {
                result = modified ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES;
            } else {
                final HighlightSeverity severity = errInfo.getHighlightDisplayLevel().getSeverity();
                Map<HighlightSeverity, SimpleTextAttributes> cache = modified ? myModifiedHighlightAttributes : myHighlightAttributes;
                result = cache.get(severity);
                if (result == null) {
                    final TextAttributesKey attrKey = SeverityRegistrar.getSeverityRegistrar(myProject).getHighlightInfoTypeBySeverity(severity).getAttributesKey();
                    TextAttributes textAttrs = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(attrKey);
                    if (modified) {
                        textAttrs = textAttrs.clone();
                        textAttrs.setFontType(textAttrs.getFontType() | Font.BOLD);
                    }
                    result = SimpleTextAttributes.fromTextAttributes(textAttrs);
                    cache.put(severity, result);
                }
            }

            if (property instanceof IntrospectedProperty) {
                final RadComponent c = mySelection.get(0);
                if (Properties.getInstance().isPropertyDeprecated(c.getModule(), c.getComponentClass(), property.getName())) {
                    return new SimpleTextAttributes(result.getBgColor(), result.getFgColor(), result.getWaveColor(),
                            result.getStyle() | SimpleTextAttributes.STYLE_STRIKEOUT);
                }
            }

            return result;
        }
    }

    /**
     * This is adapter from PropertyEditor to TableCellEditor interface
     */
    private final class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
        private PropertyEditor myEditor;

        /**
         * Sets editor.
         *
         * @param editor the editor
         */
        public void setEditor(@NotNull final PropertyEditor editor) {
            myEditor = editor;
        }

        @Override
        public Object getCellEditorValue() {
            try {
                return myEditor.getValue();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public Component getTableCellEditorComponent(final JTable table, @NotNull final Object value, final boolean isSelected, final int row, final int column) {
            final Property property = (Property) value;
            try {
                //noinspection unchecked
                final JComponent c = myEditor.getComponent(mySelection.get(0), getSelectionValue(property), null);
                if (c instanceof JComboBox) {
                    c.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
                }

                return c;
            } catch (Exception ex) {
                LOG.debug(ex);
                SimpleColoredComponent errComponent = new SimpleColoredComponent();
                errComponent.append(UIDesignerBundle.message("error.getting.value", ex.getMessage()), SimpleTextAttributes.ERROR_ATTRIBUTES);
                return errComponent;
            }
        }
    }

    /**
     * Reimplementation of LookAndFeel's SelectPreviousRowAction action.
     * Standard implementation isn't smart enough.
     *
     * @see javax.swing.plaf.basic.BasicTableUI
     */
    private final class MySelectPreviousRowAction extends AbstractAction {
        @Override
        public void actionPerformed(final ActionEvent e) {
            final int rowCount = getRowCount();
            LOG.assertTrue(rowCount > 0);
            int selectedRow = getSelectedRow();
            if (selectedRow != -1) {
                selectedRow -= 1;
            }
            selectedRow = (selectedRow + rowCount) % rowCount;
            if (isEditing()) {
                finishEditing();
                getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
                scrollRectToVisible(getCellRect(selectedRow, 0, true));
                startEditing(selectedRow);
            } else {
                getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
                scrollRectToVisible(getCellRect(selectedRow, 0, true));
            }
        }
    }

    /**
     * Reimplementation of LookAndFeel's SelectNextRowAction action.
     * Standard implementation isn't smart enough.
     *
     * @see javax.swing.plaf.basic.BasicTableUI
     */
    private final class MySelectNextRowAction extends AbstractAction {
        @Override
        public void actionPerformed(final ActionEvent e) {
            final int rowCount = getRowCount();
            LOG.assertTrue(rowCount > 0);
            final int selectedRow = (getSelectedRow() + 1) % rowCount;
            if (isEditing()) {
                finishEditing();
                getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
                scrollRectToVisible(getCellRect(selectedRow, 0, true));
                startEditing(selectedRow);
            } else {
                getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
                scrollRectToVisible(getCellRect(selectedRow, 0, true));
            }
        }
    }

    /**
     * Reimplementation of LookAndFeel's StartEditingAction action.
     * Standard implementation isn't smart enough.
     *
     * @see javax.swing.plaf.basic.BasicTableUI
     */
    private final class MyStartEditingAction extends AbstractAction {
        @Override
        public void actionPerformed(final ActionEvent e) {
            final int selectedRow = getSelectedRow();
            if (selectedRow == -1 || isEditing()) {
                return;
            }

            startEditing(selectedRow);
        }
    }

    /**
     * Expands property which has children or start editing atomic
     * property.
     */
    private final class MyEnterAction extends AbstractAction {
        @Override
        public void actionPerformed(final ActionEvent e) {
            final int selectedRow = getSelectedRow();
            if (isEditing() || selectedRow == -1) {
                return;
            }

            final Property property = myProperties.get(selectedRow);
            if (getPropChildren(property).length > 0) {
                if (isPropertyExpanded(property)) {
                    collapseProperty(selectedRow);
                } else {
                    expandProperty(selectedRow);
                }
            } else {
                startEditing(selectedRow);
            }
        }
    }

    private class MyExpandCurrentAction extends AbstractAction {
        private final boolean myExpand;

        /**
         * Instantiates a new My expand current action.
         *
         * @param expand the expand
         */
        MyExpandCurrentAction(final boolean expand) {
            myExpand = expand;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            final int selectedRow = getSelectedRow();
            if (isEditing() || selectedRow == -1) {
                return;
            }
            final Property property = myProperties.get(selectedRow);
            if (getPropChildren(property).length > 0) {
                if (myExpand) {
                    if (!isPropertyExpanded(property)) {
                        expandProperty(selectedRow);
                    }
                } else {
                    if (isPropertyExpanded(property)) {
                        collapseProperty(selectedRow);
                    }
                }
            }
        }
    }

    /**
     * Updates UI of editors and renderers of all introspected properties
     */
    private final class MyLafManagerListener implements LafManagerListener {
        /**
         * Recursively updates renderer and editor UIs of all synthetic
         * properties.
         */
        private static void updateUI(final Property property) {
            final PropertyRenderer renderer = property.getRenderer();
            renderer.updateUI();
            final PropertyEditor editor = property.getEditor();
            if (editor != null) {
                editor.updateUI();
            }
            final Property[] children = property.getChildren(null);
            for (int i = children.length - 1; i >= 0; i--) {
                final Property child = children[i];
                if (!(child instanceof IntrospectedProperty)) {
                    updateUI(child);
                }
            }
        }

        @Override
        public void lookAndFeelChanged(@NotNull final LafManager source) {
//            updateUI(myBorderProperty);
//            updateUI(MarginProperty.getInstance(myProject));
//            updateUI(HGapProperty.getInstance(myProject));
//            updateUI(VGapProperty.getInstance(myProject));
//            updateUI(HSizePolicyProperty.getInstance(myProject));
//            updateUI(VSizePolicyProperty.getInstance(myProject));
//            updateUI(HorzAlignProperty.getInstance(myProject));
//            updateUI(VertAlignProperty.getInstance(myProject));
//            updateUI(IndentProperty.getInstance(myProject));
//            updateUI(UseParentLayoutProperty.getInstance(myProject));
//            updateUI(MinimumSizeProperty.getInstance(myProject));
//            updateUI(PreferredSizeProperty.getInstance(myProject));
//            updateUI(MaximumSizeProperty.getInstance(myProject));
//            updateUI(myButtonGroupProperty);
//            updateUI(myLayoutManagerProperty);
//            updateUI(SameSizeHorizontallyProperty.getInstance(myProject));
//            updateUI(SameSizeVerticallyProperty.getInstance(myProject));
//            updateUI(CustomCreateProperty.getInstance(myProject));
//            updateUI(ClientPropertiesProperty.getInstance(myProject));
        }
    }


}
